# -*- coding: utf-8 -*-
import re
from common import read_config, datetime_util, faker0
from common.debug import result_log
from common.faker0 import Fake
from common.logget import logger
import os
import jsonpath
from common.read_yaml import get_yaml_data, write_yaml, replace_content
from common.db import DB

# 项目文件路径
api_path = os.path.dirname(os.path.dirname(__file__))  # 获取项目路径 C:/Users/Administrator/.PyCharmCE2019.2/py_test
case_path = os.path.join(api_path, 'case')  # 测试脚本所在路径 C:/Users/Administrator/.PyCharmCE2019.2/py_test\case
data_path = os.path.join(api_path, 'data')  # 测试数据所在路径 C:/Users/Administrator/.PyCharmCE2019.2/py_test\data
logs_path = os.path.join(api_path, 'logs')  # 日志文件所在路径
report_path = os.path.join(api_path, 'report')  # 日志文件所在路径

# 获取配置文件需要执行的项目目录
cof = read_config.Docof()
exe_name = cof.get_value_from_option("environment", "execute")  # 执行的文件名称 也是存放用例的文件名
# 获取需要访问的ip地址
ip = cof.get_value_from_option("environment", exe_name)

sys = cof.get_value_from_option("environment", "sys_name")  # 项目系统名称
# linux配置
host = cof.get_value_from_option("linux", "host")  # linux地址
user = cof.get_value_from_option("linux", "user")  # linux 用户名
passwd = cof.get_value_from_option("linux", "passwd")  # linux 密码
zip_file_path = cof.get_value_from_option("linux", "zip_file_path")  # linux报告文件存放地址
Access_address = cof.get_value_from_option("linux", "Access_address")  # linux报告文件访问地址


# 存放用例的目录路径
business_path = os.path.join(data_path, f'{exe_name}/BusinessFlow')
single_path = os.path.join(data_path, f'{exe_name}/single')
FileUpload_path = os.path.join(data_path, f'{exe_name}/FileUpload')


# 清理日志文件
def delete_files_in_directory(directory):
    # 遍历目录中的所有文件
    for filename in os.listdir(directory):
        file_path = os.path.join(directory, filename)
        try:
            # 如果是文件，则删除
            if os.path.isfile(file_path):
                os.remove(file_path)
                print(f"文件 {file_path} 已删除")
        except OSError as e:
            print(f"错误: {file_path} : {e.strerror}")


# 返回目录下所有文件名，以列表返回
def filename_all(path):
    num = os.listdir(path)
    return num

# 获取响应参数(可以提取多个根据yaml文件的 response_extraction来判断)，自定义键然后写入文件/data/extract.yaml 文件

def assert_response(response, validate, name):
    """
    断言接口响应

    Args:
        response: 接口响应对象
        validate: 断言规则列表
        name: 接口名称
    """
    logger.info(f"接口返回文本信息: {response.content.decode()}")
    logger.info("开始断言--->>>>")

    for assertion in validate:
        try:
            _process_single_assertion(assertion, response, name)
        except AssertionError as e:
            logger.error(f"断言失败: {str(e)}")
            raise

    logger.info(f"接口测试结果: {name} ---->>>>>>测试通过 <<<<<<")
    logger.info(f"==================================接口用例执行结束: {name}==================================")


def _process_single_assertion(assertion, response, name):
    """处理单个断言规则"""
    if "eq" in assertion:
        _assert_equal(assertion["eq"], response, name)
    elif "contain" in assertion:
        _assert_contain(assertion["contain"], response, name)
    elif "db" in assertion:
        _assert_database(assertion["db"], name)
    elif "regex_match" in assertion:
        _assert_regex(assertion["regex_match"], response, name)


def _assert_equal(eq_assertion, response, name):
    """等值断言"""
    json_path, expected = eq_assertion
    actual_result = jsonpath.jsonpath(response.json(), json_path)

    if not actual_result:
        raise AssertionError(f"JSONPath表达式未找到数据: {json_path}")

    actual_value = actual_result[0]
    assert actual_value == expected, (
        f"接口标题：{name}(等值断言失败)-->>"
        f"期望结果【{expected}】 ≠ 实际结果【{actual_value}】"
    )
    logger.info(f"---->>>>{name}接口: {expected} 等值断言成功")


def _assert_contain(contain_assertion, response, name):
    """包含断言"""
    expected_text = contain_assertion[0]
    actual_text = response.text

    assert expected_text in actual_text, (
        f"接口标题：{name}(文本包含断言失败)-->>"
        f"期望文本【{expected_text}】不在实际结果中"
    )
    logger.info(f"---->>>>{name}接口: {expected_text} 文本包含断言成功")


def _assert_database(db_assertion, name):
    """数据库断言"""
    sql, expected_count = db_assertion
    db = DB()
    result = db.query(sql)

    assert len(result) == expected_count, (
        f"数据库断言失败: SQL【{sql}】"
        f"期望记录数【{expected_count}】≠ 实际记录数【{len(result)}】"
    )
    logger.info(f"---->>>>{name}接口: 数据库断言成功")


def _assert_regex(regex_assertion, response, name):
    """正则表达式断言"""
    # 待实现
    pass



def get_resp(response, extract_key, name):
    """
    :param response:     请求的响应数据
    :param extract_key:  需要写入yaml文件的自定义关键字加值  key：value
    :return:01
    """

    if extract_key:
        try:
            for i in extract_key:
                if "rex" in i.keys():

                    yaml_result = i.get("rex")[0]  # jsonpath提取器定位方法

                    # actual_result = jsonpath.jsonpath(response.json(),f'$..{yaml_result}')#去掉 $..
                    # 是考虑返回的json数据如果存在两个一样的id
                    extract_value = jsonpath.jsonpath(response.json(),
                                                      yaml_result)  # jsonpath提取的值  取到的值返回在列表里 如 [200]

                    input_key = i.get("rex")[1]  # 自定义的键
                    # print(input_key)
                    new_dict = {input_key: extract_value[0]}
                    # write_yaml(new_dict)  # 写入yaml文件
                    replace_content(new_dict)  # 此方式可以替换相同的字典

                    logger.info(
                        f"正在注入变量>>>>>> 成功提取响应数据，并自定义关键字{new_dict}>>>>写入extract.yaml关联文件")

                elif "re" in i.keys():  # 正则表达式
                    yaml_result = i.get("re")[0]
                    ex_value = re.search(yaml_result, response.text())
                    input_key = i.get("rex")[1]  # 自定义的键
                    # print(input_key)
                    new_dict = {input_key: ex_value}
                    # write_yaml(new_dict)  # 写入yaml文件
                    replace_content(new_dict)  # 此方式可以替换相同的字典
                    logger.info(
                        f"正在注入变量>>>>>> 成功提取响应数据，并自定义关键字{new_dict}>>>>写入extract.yaml关联文件")
                    pass

        except Exception as e:
            # logger.info(f"jsonpath （响应数据）提取失败__请检查{extract_key}写法是否正确。异常信息为：{e}，接口返回值为：{response.json()}")
            logger.exception(
                f"jsonpath （响应数据）提取失败__请检查 {yaml_result} 写法是否正确。异常信息为：{e}，接口返回值文本信息为：{response.content.decode()}")
            raise AssertionError(
                f"用例名称：{name}-->（接口响应数据）提取失败_请检查 {yaml_result} 写法是否正确。异常信息为：{e}，接口返回值文本信息为：{response.content.decode()}")  # 抛出异常后  程序终止 后面的程序不在执行
            # print(f"jsonpath提取器提取失败请检查写法是否正确。异常信息为：{e}")


# 此函数作用是提取/data/extract.yaml 文件的指定关键字，再发送请求前添加至请求体 做接口的关联
def change_data(case) -> list:
    """

    :param case: 提取关键字数据。更新在入参的字典中  或 拼接在url后面（delete）
    case={'json': {'pageNum': 1, 'pageSize': 20},'response_extraction': [{'rex': ['$.id', 'yqid']}], 'relation': {'id': 'yqid', 'na': 'ida'}, 'expected': [{'eq': ['$.code', 200]}, {'eq': ['$.message', '请求成功']}]}
    :return: 返回处理后 待发送的数据
    """
    try:
        logger.info(
            f"""
                                           _    _         _      _____         _
                            __ _ _ __ (_)  / \\  _   _| |_ __|_   _|__  ___| |_
                           / _` | '_ \\| | / _ \\| | | | __/ _ \\| |/ _ \\/ __| __|
                          | (_| | |_) | |/ ___ \\ |_| | || (_) | |  __/\\__ \\ |_
                           \\__,_| .__/|_/_/   \\_\\__,_|\\__\\___/|_|\\___||___/\\__|
                                |_|
                                开始执行{sys}项目...
                              """
        )
        # 打印日志-----------------
        name = case['title']
        logger.info(f"==================================接口用例开始执行: {name}==================================")
        logger.info(f"接口请求标题>>>>>> {name}")
        method = case['request']['method']
        uri = case['request']['url']
        if "$" in str(uri):
            uri = _replace_template_in_string(uri)
        url = ip + uri  # 拼接最终发送请求的路径
        logger.info(f"接口请求地址>>>>>> {method}  {url}")
        # 更新鉴权码 token
        header = case['headers']
        replace_nested_dict_value(header)
        # try:
        #     extract1 = get_yaml_data(data_path + '/extract.yaml')
        #     token = {'Authorization': extract1['Authorization']}  # login_token提取 存入的变量为Authorization
        #     # 以字典的形式 直接更新在headers里面
        #     header.update(token)
        # except KeyError as e:  # extract 文件为空时会报错，普捉解决
        #     logger.info('本次未获取 Authorization认证信息')
        # logger.info(f"接口请求头  >>>>>> {header}")
        # # token =  extract1['Authorization']

        data = case['json']

        if method == 'file':  # 处理文件body
            data = {"file": (data["filename"], open(data["file_path"], "rb"), data["content_type"])}

        # 提取数据做接口关联  注释了 暂时不用 可以$来提取变量 relation字段一直置空就行
        # relation = case.get('relation')  # 入参的键需要参考文档 提前写好
        # if relation:  # 如果不为空, 则需要提取响应数据 做关联
        #     """
        #     做接口关联，可以通过json[key]=value 的形式 进行套娃式的添加数据
        #     j = {i: value[relation[i]]}  # i 为系统入参需要的键 id， relation[i] 为获取自定义关键字的键 是yqid
        #     data.update(j)  # post、get请求 则 更新请求体数据
        #     """
        #     value = get_yaml_data(
        #         data_path + '/extract.yaml')  # error提示  写../data/extract.yaml路径在跑pytest框架会出错 会在读取文件后返回None
        #
        #     for i in relation:
        #         if method.lower() == 'delete':
        #             uri = uri + value[relation[i]]  # 在Python中,字符串属于不可变对象,不支持原地修改,如果需要修改其中的值,只能重新创建一个新的字符串对象
        #
        #         else:
        #
        #             data[i] = value[relation[i]]  # 对单层字典进行替换
        #             logger.info(f"正在提取接口关联数据>>>>>> 成功提取数据{data}>>>>并替换到请求正文中")
        # url = ip + uri  # 拼接最终发送请求的路径
        if data:  # 判断json数据是否为空，如get请求（大多数get请求也需要关联ID 做查询）
            replace_nested_dict_value(data)
        logger.info(f"接口请求正文>>> >> {data}")

        return [name, url, method, data, header]  # 返回处理后的数据

    except Exception as e:
        logger.exception(f"change_data方法出错了哦。。error>>:{e}..")


# def replace_nested_dict_value(nested_dict):  # 把字典value的某些数据 替换成 接口关联数据 ，做入参
#     for key, value in nested_dict.items():
#         if isinstance(value, dict):
#             replace_nested_dict_value(value)
#         elif isinstance(value, list):  # "emergencyDrillsFiles": [{"name": "1.jpg"}] value为列表嵌套的字典
#             for item in value:
#                 if isinstance(item, (dict, list)):  # 如果列表里面装的是字典  遇到过如下情况列表装字符串 一取值运行就报错 [ '动火设备内部构件清理干净' ]
#                     replace_nested_dict_value(item)
#
#         # elif "$" in str(value):  # old_value = ['$temp_path','$Authorization']
#         #     new_string = value.replace("$", "")
#         #     print(new_string)
#         #     # 生成随机数并替换字典值，具体方法请查看Fake()类
#         #     fake1 = Fake()
#         #     if hasattr(fake1, new_string):  # 使用hasattr检查类是否具有特定的方法
#         #         randoms = getattr(fake1, new_string)()
#         #         nested_dict[key] = randoms
#         #         logger.info(f"使用函数助手fake1---{new_string}方法>>>>入参数据如下---- {key}:{randoms}")
#         #         print(randoms)
#         #     elif hasattr(datetime_util, new_string):  # 使用hasattr检查 日期时间类是否具有特定的函数
#         #         randoms = getattr(datetime_util, new_string)()
#         #         nested_dict[key] = randoms
#         #         logger.info(f"使用函数助手datetime_util---{new_string}方法>>>>入参数据如下---- {key}:{randoms}")
#         #     else:
#         #         # 接口关联数据读取并替换字典值
#         #         extract1 = get_yaml_data(data_path + '/extract.yaml')
#         #         new_value = extract1[new_string]
#         #         logger.info(f"正在提取接口关联数据>>>>>> 成功提取数据{key}:{new_value}>>>>并替换到请求正文中")
#         #         nested_dict[key] = new_value
#
#         elif re.search(r'\$\{[^}]*}', str(value)):  # 可以使用${} 替换数据并拼接新的数据如 ${today}特级动火
#
#             match = re.search(r'{\s*([^}]+)\s*}', str(value))  # {te}
#             if match:
#                 a = match.group(1)  # 返回匹配到的第一个组，即 {} 中的内容
#                 fake1 = Fake()
#                 if hasattr(datetime_util, a):  # 使用hasattr检查 日期时间类是否具有特定的函数
#                     times = getattr(datetime_util, a)()  # 获取函数返回值
#                     temp = str(value).replace(a, str(times))  # 替换到模版上/ction/${15152522}
#                     result = re.sub(r'\$\{([^}]+)}', r'\1', temp)  # 去掉 $｛｝
#                     nested_dict[key] = result
#                     logger.info(f"使用日期函数助手datetime_util---{a}方法>>>>入参数据如下---- {key}:{result}")
#
#                 elif hasattr(fake1, a):
#                     randoms = getattr(fake1, a)()
#                     temp = str(value).replace(a, str(randoms))  # 替换到模版上/ction/${15152522}
#                     result = re.sub(r'\$\{([^}]+)}', r'\1', temp)  # 去掉 $｛｝
#                     nested_dict[key] = result
#                     logger.info(f"使用随机生成函数助手fake1---{a}方法>>>>入参数据如下---- {key}:{result}")
#
#                 else:
#                     extract = get_yaml_data(data_path + '/extract.yaml')[a]  # 提取出关联数据
#                     temp = str(value).replace(a, str(extract))  # 替换到模版上/ction/${15152522}
#                     result = re.sub(r'\$\{([^}]+)}', r'\1', temp)  # 去掉 $｛｝
#                     nested_dict[key] = result
#                     logger.info(f"正在提取接口关联数据>>>>>> 成功提取数据{key}:{result}>>>>并替换到请求正文中")
#             else:
#                 print(" match 未匹配到")
def replace_nested_dict_value(nested_dict):
    """
    递归替换数据中的模板变量 - 优化版
    支持字典、列表以及列表中的字符串替换

    Args:
        nested_dict: 要处理的数据（字典、列表或基本类型）
    """
    if isinstance(nested_dict, dict):
        for key, value in nested_dict.items():
            nested_dict[key] = _replace_single_value(value)
    elif isinstance(nested_dict, list):
        for i, item in enumerate(nested_dict):
            nested_dict[i] = _replace_single_value(item)


def _replace_single_value(value):
    """替换单个值中的模板变量"""
    # 如果是字典或列表，递归处理   #处理成一层的元素
    if isinstance(value, (dict, list)):
        replace_nested_dict_value(value)
        return value

    # 如果是字符串且包含模板，进行替换
    if isinstance(value, str) and "${" in value:
        return _replace_template_in_string(value)

    # 其他类型直接返回
    return value

def _replace_template_in_string(value):
    """替换字符串中的模板变量（现在也用于URL处理）"""
    # 匹配 ${variable} 格式的模板
    # 举例：value = "作业：${today}"则 matches = ["today"]  # 正则匹配到的变量名
    template_pattern = r'\$\{([^}]+)\}'
    matches = re.findall(template_pattern, value)

    if not matches:
        return value

    result = value
    for match in matches: #如果多个则循环替换
        # 1. 获取替换值
        replacement = _get_template_replacement(match)
        # 2. 执行替换
        result = result.replace(f'${{{match}}}', str(replacement))
        # 这里 f'${{{match}}}' 会生成 "${today}" 这样的字符串
        # 然后用实际值替换整个 "${today}" 模板

    return result


def _get_template_replacement(template):
    """获取模板变量的替换值"""
    # 检查日期时间函数
    if hasattr(datetime_util, template):
        replacement = getattr(datetime_util, template)()
        logger.info(f"使用日期函数: {template} -> {replacement}")
        return replacement

    # 检查随机数据函数
    fake = Fake()
    if hasattr(fake, template):
        replacement = getattr(fake, template)()
        logger.info(f"使用随机函数: {template} -> {replacement}")
        return replacement

    # 从提取文件中获取关联数据
    extract_data = get_yaml_data(data_path + '/extract.yaml')
    if template in extract_data:
        replacement = extract_data[template]
        logger.info(f"使用关联数据: {template} -> {replacement}")
        return replacement

    # 如果找不到替换值，记录警告但返回原模板（避免报错中断测试）
    logger.warning(f"未找到模板变量替换值: {template}")
    return f"${{{template}}}"




